package com.lagou.mvc.servlet;

import com.lagou.mvc.annotation.*;
import com.lagou.mvc.pojo.Handler;
import com.lagou.utils.ClassUtil;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 创建人: yyn
 * 创建时间:2021/8/2 21:12
 */
public class DaspatcherServlet extends HttpServlet {

    private Properties properties = new Properties();
    private Set<Class<?>> classSet = new HashSet<>();
    private Map<String, Object> ioc = new HashMap<>();
    private Map<String, Handler> handlerMapping = new HashMap<>();

    @Override
    public void init(ServletConfig config) {
        //// 1 加载配置文件 springmvc.properties
        String context = config.getInitParameter("context");
        doConfig(context);
        // 2 扫描相关的类，扫描注解
        doScan(properties.getProperty("scanPackage"));

        // 3 初始化bean对象（实现ioc容器，基于注解）
        doIcoInit();

        // 4 实现依赖注入
        doAutowired();

        // 5 构造一个HandlerMapping处理器映射器，将配置好的url和Method建立映射关系
        initHandler();

    }

    private void initHandler() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> objectEntry : ioc.entrySet()) {
            Class<?> aClass = objectEntry.getValue().getClass();
            String path = null;
            if (aClass.isAnnotationPresent(RequestMapping.class)) {
                path = aClass.getAnnotation(RequestMapping.class).value();
            }
            for (Method declaredMethod : aClass.getDeclaredMethods()) {
                if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {
                    // 处理
                    RequestMapping annotation = declaredMethod.getAnnotation(RequestMapping.class);
                    path += annotation.value();
                    Handler handler = new Handler(objectEntry.getValue(), declaredMethod, Pattern.compile(path));

                    Parameter[] parameters = declaredMethod.getParameters();
                    for (int j = 0; j < parameters.length; j++) {
                        Parameter parameter = parameters[j];

                        if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                            handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), j);
                        } else {
                            handler.getParamIndexMapping().put(parameter.getName(), j);
                        }

                    }
                    handlerMapping.put(path, handler);

                }
            }
        }
    }

    private void doAutowired() {
        try {
            for (Object value : ioc.values()) {
                Field[] declaredFields = value.getClass().getDeclaredFields();
                for (Field declaredField : declaredFields) {
                    if (declaredField.isAnnotationPresent(Autowire.class)) {
                        Autowire annotation = declaredField.getAnnotation(Autowire.class);
                        String beanName = annotation.value();
                        if (beanName.trim().equals("")) {
                            beanName = declaredField.getType().getName();
                        }
                        declaredField.set(value, ioc.get(beanName));
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 派发请求
        String requestURI = req.getRequestURI();
        Handler handler = handlerMapping.get(requestURI);
        if(handler == null){
            resp.setStatus(404);
            return;
        }



        // 参数绑定
        // 获取所有参数类型数组，这个数组的长度就是我们最后要传入的args数组的长度
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();


        // 根据上述数组长度创建一个新的数组（参数数组，是要传入反射调用的）
        Object[] args = new Object[parameterTypes.length];

        // 以下就是为了向参数数组中塞值，而且还得保证参数的顺序和方法中形参顺序一致

        Map<String, String[]> parameterMap = req.getParameterMap();

        // 遍历request中所有参数  （填充除了request，response之外的参数）
        for(Map.Entry<String,String[]> param: parameterMap.entrySet()) {
            // name=1&name=2   name [1,2]
            String value = StringUtils.join(param.getValue(), ",");  // 如同 1,2

            // 如果参数和方法中的参数匹配上了，填充数据
            if(!handler.getParamIndexMapping().containsKey(param.getKey())) {continue;}

            // 方法形参确实有该参数，找到它的索引位置，对应的把参数值放入paraValues
            Integer index = handler.getParamIndexMapping().get(param.getKey());//name在第 2 个位置

            args[index] = value;  // 把前台传递过来的参数值填充到对应的位置去

        }


        Integer requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        if (requestIndex != null) {
            args[requestIndex] = req;
        }



        Integer responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        if (responseIndex != null) {
            args[responseIndex] = resp;
        }
        // 添加拦截器
        Method method = handler.getMethod();
        if (method.isAnnotationPresent(Security.class)) {
            if (args.length==0) {
                resp.setStatus(401);
                PrintWriter writer = resp.getWriter();
                writer.print("do not have permission");
                return;
            }
            boolean flag = false;
            // 如果有则执行拦截器
            Security annotation = method.getAnnotation(Security.class);
            String[] value = annotation.value();
            List<String> strings = Arrays.asList(value);
            for (Object arg : args) {
                boolean contains = strings.contains(arg);
                if (contains) {
                    flag = contains;
                }
            }
            if (!flag) {
                resp.setStatus(401);
                PrintWriter writer = resp.getWriter();
                writer.print("do not have permission");
                return;
            }
        }

        try {
            Object invoke = handler.getMethod().invoke(handler.getObject(), args);
            PrintWriter writer = resp.getWriter();
            writer.print(invoke);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private void doConfig(String context) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(context);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void doScan(String scanPackage) {
        try {
            this.classSet = ClassUtil.getClasses(scanPackage);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doIcoInit() {
        if (classSet.isEmpty()) {
            return;
        }
        try {
            for (Class<?> aClass : classSet) {
                if (aClass.isAnnotationPresent(Controller.class)) {
                    String simpleName = aClass.getSimpleName();
                    String lowerFirst = ClassUtil.lowerFirst(simpleName);
                    ioc.put(lowerFirst, aClass.newInstance());
                } else if (aClass.isAnnotationPresent(Service.class)) {
                    String trim = aClass.getAnnotation(Service.class).value().trim();
                    if (!trim.equals("")) {
                        ioc.put(trim, aClass.newInstance());
                    } else {
                        String simpleName = aClass.getSimpleName();
                        String lowerFirst = ClassUtil.lowerFirst(simpleName);
                        ioc.put(lowerFirst, aClass.newInstance());
                    }

                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        String name = anInterface.getName();
                        ioc.put(name, aClass.newInstance());
                    }

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }


    }
}
